/*

Miranda IM: the free IM client for Microsoft* Windows*

Copyright 2000-2007 Miranda ICQ/IM project, 
all portions of this codebase are copyrighted to the people 
listed in contributors.txt.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.	

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/

#define XMLCARD_VERSION	"1.1"

/**
 * system & local includes:
 **/
#include "commonheaders.h"
#include "fileXML.h"

namespace NExImport {

LRESULT CALLBACK DlgProc_DataHistory(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
	switch(msg) 
	{
		case WM_INITDIALOG:
		{
			const NIcoLib::ICONCTRL idIcon[] = {
				{ ICO_DLG_EXPORT,	WM_SETICON,		NULL		},
				{ ICO_DLG_EXPORT,	STM_SETIMAGE,	ICO_DLGLOGO	},
				{ ICO_BTN_EXPORT,	BM_SETIMAGE,	IDOK		},
				{ ICO_BTN_CANCEL,	BM_SETIMAGE,	IDCANCEL	}
			};
			const INT numIconsToSet = DBGetByte(SET_ICONS_BUTTONS, 1) ? SIZEOF(idIcon) : 2;
			NIcoLib::SetCtrlIcons(hDlg, idIcon, numIconsToSet);

			TranslateDialogDefault(hDlg);
			SendDlgItemMessage(hDlg, IDOK, BUTTONTRANSLATE, NULL, NULL);
			SendDlgItemMessage(hDlg, IDCANCEL, BUTTONTRANSLATE, NULL, NULL);
			break;
		}
		case WM_CTLCOLORSTATIC:
			switch(GetWindowLong((HWND)lParam, GWL_ID)) {
				case STATIC_WHITERECT:
				case ICO_DLGLOGO:
				case IDC_INFO:
					SetBkColor((HDC)wParam, RGB(255, 255, 255));
					return (BOOL)GetStockObject(WHITE_BRUSH);
			}
			return FALSE;
		case WM_COMMAND:
			if(HIWORD(wParam) == BN_CLICKED ) {
				switch(LOWORD(wParam)) {
					case IDCANCEL:
						EndDialog(hDlg, 0);
						break;
					case IDOK: {
						WORD hiWord = 0;

						if(IsDlgButtonChecked(hDlg, IDC_CHECK1))
							hiWord |= EXPORT_DATA;
						if(IsDlgButtonChecked(hDlg, IDC_CHECK2))
							hiWord |= EXPORT_HISTORY;
						EndDialog(hDlg, (INT_PTR)MAKELONG(IDOK, hiWord));
						break;
					}
				}
			}
			break;
	}
	return FALSE;
}

/***********************************************************************************************************
 * exporting stuff
 ***********************************************************************************************************/

/**
 * name:	Export
 * desc:	globally accessible function which does the whole export stuff.
 * params:	hContact	- handle to the contact who is to export
 *			pszFileName	- full qualified path to the xml file which is destination for the export process
 * return:	0 on success, 1 otherwise
 **/
INT CFileXml::Export(HANDLE hContact, LPCSTR pszFileName)
{
	FILE *xmlfile;
	ENUMLIST Modules;
	LONG cbHeader;
	SYSTEMTIME now;

	DWORD result = (DWORD)DialogBox(ghInst, MAKEINTRESOURCE(IDD_EXPORT_DATAHISTORY), NULL, (DLGPROC)DlgProc_DataHistory);
	if(LOWORD(result) != IDOK)
		return 0;
	_wExport = HIWORD(result);

	// show dialog to enable user to select modules for export
	if(!(_wExport & EXPORT_DATA) || !SelectModulesToExport(hContact, &Modules, NULL)) {

		xmlfile = fopen(pszFileName, "wt");
		if(!xmlfile) {
			MsgErr(NULL, _T("Can't create xml file!\n%S"), pszFileName);
			return 1;
		}
		
		GetLocalTime(&now);

		// write xml header raw as it is without using the tinyxml api
		fprintf(xmlfile, 
			"%c%c%c<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
			"<XMLCard ver=\""XMLCARD_VERSION"\" ref=\"%04d-%02d-%02d %02d:%02d:%02d\">\n",
			0xefU, 0xbbU, 0xbfU, now.wYear, now.wMonth, now.wDay, now.wHour, now.wMinute, now.wSecond
		);
		// remember the header's size
		cbHeader = ftell(xmlfile);

		_hContactToWorkOn = hContact;
		CExImContactXML vContact(this);
		
		// write data
		if(_hContactToWorkOn == INVALID_HANDLE_VALUE) {
#ifdef _DEBUG
			LARGE_INTEGER freq, t1, t2;

			QueryPerformanceFrequency(&freq);
			QueryPerformanceCounter(&t1);
#endif
			// export owner contact
			if(vContact.fromDB(NULL)) {
				vContact.Export(xmlfile, &Modules);
			}
			for(hContact = (HANDLE)CallService(MS_DB_CONTACT_FINDFIRST, 0, 0);
				hContact != NULL;
				hContact = (HANDLE)CallService(MS_DB_CONTACT_FINDNEXT, (WPARAM)hContact, 0))
			{
				// ignore meta subcontacts by default
				if(!myGlobals.HaveMetaContacts || !DBGetSettingByte(hContact, META_PROTO, "IsSubcontact", 0))
					if(vContact.fromDB(hContact)) {
						vContact.Export(xmlfile, &Modules);
					}
			}
#ifdef _DEBUG
			QueryPerformanceCounter(&t2);
			MsgErr(NULL, _T("Export took %f msec"),
				(long double)(t2.QuadPart - t1.QuadPart) / freq.QuadPart * 1000.);
#endif
		}
		// export single contact
		else if(vContact.fromDB(hContact)) {
			vContact.Export(xmlfile, &Modules);
		}

		// nothing exported?
		if(cbHeader == ftell(xmlfile)) {
			fclose(xmlfile);
			DeleteFileA(pszFileName);
			return 1;
		}
		fputs("</XMLCard>\n", xmlfile);
		fclose(xmlfile);
	}
	return 0;
}


/***********************************************************************************************************
 * importing stuff
 ***********************************************************************************************************/

CFileXml::CFileXml()
{
	_numContactsTodo = 0;
	_numContactsDone = 0;
	_numSettingsTodo = 0;
	_numSettingsDone = 0;
	_numEventsTodo = 0;
	_numEventsDone = 0;
	_numEventsDuplicated = 0;
}

/**
 * name:	ImportOwner
 * desc:	Interpretes an xmlnode as owner contact, finds a corresponding contact in database
 *			or adds a new one including all xml childnodes.
 * params:	xContact	- xmlnode representing the contact
 *			stat		- structure used to collect some statistics
 * return:	ERROR_OK on success or one other element of ImportError to tell the type of failure
 **/
INT CFileXml::ImportOwner(TiXmlElement* xContact)
{
	CExImContactXML vContact(this);

	if(vContact = xContact) {
		vContact.Import();
		return ERROR_OK;
	}
	return ERROR_NOT_ADDED;
}

/**
 * name:	ImportContacts
 * desc:	Parse all child nodes of an given parent node and try to import all found contacts
 * params:	xmlParent	 - xmlnode representing the parent of the list of contacts
 *			hContact	 - handle to the contact, who is the owner of the setting to import
 *			stat		 - structure used to collect some statistics
 * return:	ERROR_OK if at least one contact was successfully imported
 **/
INT CFileXml::ImportContacts(TiXmlElement* xmlParent)
{
	TiXmlElement *xContact;
	CExImContactXML vContact(this);
	INT result;
	LPTSTR pszNick;

	// import contacts
	for(xContact = xmlParent->FirstChildElement(); xContact != NULL; xContact = xContact->NextSiblingElement()) {
		if(!mir_stricmp(xContact->Value(), XKEY_CONTACT)) {
			// update progressbar and abort if user clicked cancel
			pszNick = Utf8ToTChar(xContact->Attribute("nick"));
			// user clicked abort button
			if(_progress.UpdateContact(_T("Contact: %s (%S)"), pszNick, xContact->Attribute("proto"))) {
				result = vContact.LoadXmlElemnt(xContact);
				switch(result) {
					case ERROR_OK:
						// init contact class and import if matches the user desires
						if(_hContactToWorkOn == INVALID_HANDLE_VALUE || vContact.handle() == _hContactToWorkOn) {
							result = vContact.Import(_hContactToWorkOn != INVALID_HANDLE_VALUE);
							switch(result) {
								case ERROR_OK:
									_numContactsDone++;
									break;
								case ERROR_ABORTED:
									if(pszNick) mir_free(pszNick);
									return ERROR_ABORTED;
#ifdef _DEBUG
								default:
									MsgErr(NULL, _T("Importing %s caused error %d"), pszNick, result);
									break;
#endif
							}
						}
						break;
					case ERROR_ABORTED:
						if(pszNick) mir_free(pszNick);
						return ERROR_ABORTED;
#ifdef _DEBUG
					default:
						MsgErr(NULL, _T("Loading contact %s from xml failed with error %d"), pszNick, result);
						break;
#endif
				}
			}
			if(pszNick) mir_free(pszNick);
		}
		// import owner contact
		else if(_hContactToWorkOn == INVALID_HANDLE_VALUE && !mir_stricmp(xContact->Value(), XKEY_OWNER) && (vContact = xContact)) {
			result = vContact.Import();
			switch(result) {
				case ERROR_OK:
					_numContactsDone++;
					break;
				case ERROR_ABORTED:
					return ERROR_ABORTED;
#ifdef _DEBUG
				default:
					MsgErr(NULL, _T("Importing Owner caused error %d"), result);
#endif
			}
		}
	}
	return ERROR_OK;
}

/**
 * name:	CountContacts
 * desc:	Counts the number of contacts stored in the file
 * params:	xContact	- the contact, who is the owner of the keys to count
 * return:	nothing
 **/
DWORD CFileXml::CountContacts(TiXmlElement* xmlParent)
{
	DWORD dwCount = 0;
	TiXmlNode *xContact;

	try {
		// count contacts in file for progress bar
		for(xContact = xmlParent->FirstChild(); xContact != NULL; xContact = xContact->NextSibling()) {
			if(!mir_stricmp(xContact->Value(), XKEY_CONTACT) || !mir_stricmp(xContact->Value(), XKEY_OWNER)) {
				dwCount += CountContacts(xContact->ToElement()) + 1;
			}
		}
	}
	catch(...) {
		return 0;
	}
	return dwCount;
}

/**
 * name:	Import
 * desc:	Interpretes an xmlnode as owner contact, finds a corresponding contact in database
 *			or adds a new one including all xml childnodes.
 * params:	hContact	- handle to the contact, who is the owner of the setting to import
 *			pszFileName	- full qualified path to the xml file which is to import
 * return:	ERROR_OK on success or one other element of ImportError to tell the type of failure
 **/
INT CFileXml::Import(HANDLE hContact, LPCSTR pszFileName)
{
	TiXmlDocument doc;
	TiXmlElement *xmlCard = NULL;

	try {
		_hContactToWorkOn = hContact;
		// load xml file
		if(!doc.LoadFile(pszFileName)) {
			MsgErr(NULL, _T("Parser is unable to load XMLCard \"%s\"\nError: %d\nDescription: %s"),
				pszFileName, doc.ErrorId(), doc.ErrorDesc());
			return 1;
		}
		// is xmlfile a XMLCard ?
		if((xmlCard = doc.FirstChildElement("XMLCard")) == NULL) {
			MsgErr(NULL, _T("The selected file is no valid XMLCard"));
			return 1;
		}
		// check version
		if(mir_strcmp(xmlCard->Attribute("ver"), XMLCARD_VERSION)) {
			MsgErr(NULL, _T("The version of the XMLCard is not supported by UserInfoEx"));
			return 1;
		}

		// is owner contact to import ?
		if(_hContactToWorkOn == NULL) {
			INT ret;
			
			// disable database safty mode to speed up the operation
			CallService(MS_DB_SETSAFETYMODE, 0, 0);
			// import owner contact
			ret = ImportOwner(xmlCard->FirstChildElement(XKEY_OWNER));
			// as soon as possible enable safty mode again!
			CallService(MS_DB_SETSAFETYMODE, 1, 0);

			if(!ret) {
				MsgBox(NULL, MB_ICONINFORMATION, 
					_T("Complete"),
					_T("Import complete"),
					_T("Owner contact successfully imported."));
				return 0;
			} else {
				MsgErr(NULL, _T("Selected XMLCard does not contain an owner contact!"));
				return 1;
			}
		}
		else {
#ifdef _DEBUG
			LARGE_INTEGER freq, t1, t2;

			QueryPerformanceFrequency(&freq);
			QueryPerformanceCounter(&t1);
#endif
			// count contacts in file for progress bar
			_numContactsTodo = CountContacts(xmlCard);
			if(_numContactsTodo > 0) {
				_progress.SetContactCount(_numContactsTodo);
				// disable database safty mode to speed up the operation
				CallService(MS_DB_SETSAFETYMODE, 0, 0);
				// import the contacts
				ImportContacts(xmlCard);
				// as soon as possible enable safty mode again!
				CallService(MS_DB_SETSAFETYMODE, 1, 0);
			}
			// finally hide the progress dialog
			_progress.Hide();

#ifdef _DEBUG
			QueryPerformanceCounter(&t2);
			MsgErr(NULL, _T("Import took %f msec"),
				(long double)(t2.QuadPart - t1.QuadPart) / freq.QuadPart * 1000.);
#endif
			// show results
			MsgBox(NULL, MB_ICONINFORMATION, _T("Import complete"), _T("Some basic statistics"), 
				_T("added contacts: %u / %u\nadded settings: %u / %u\nadded events %u / %u\nduplicated events: %u"),
				_numContactsDone, _numContactsTodo,
				_numSettingsDone, _numSettingsTodo,
				_numEventsDone, _numEventsTodo,
				_numEventsDuplicated);
			
		}
	}	
	catch(...) {
		MsgErr(NULL, _T("FATAL: An exception was thrown while importing contacts from xmlCard!"));
		return 1;
	}
	return 0;
}

} // namespace NExImport
